/**
 * Copyright (C) 2010 DustedPixels.com
 */
package com.dustedpixels.cirt.model.cpu.z80;

import com.dustedpixels.cirt.model.core.Circuit;
import com.dustedpixels.cirt.model.core.In;
import com.dustedpixels.cirt.model.core.Internal;
import com.dustedpixels.cirt.model.core.Out;
import com.dustedpixels.cirt.model.core.Pins;
import com.dustedpixels.cirt.model.core.Script;

/**
 * Z80 core: holds internal state of Z80.
 *
 * @author Michal Pociecha-Los (micapolos@gmail.com)
 */
@Circuit
public final class Z80Core {
  public static final int ADDRESS_MUX_NONE  = 0;
  public static final int ADDRESS_MUX_PC    = 1;
  public static final int ADDRESS_MUX_SP    = 2;
  public static final int ADDRESS_MUX_REGS  = 3;
  public static final int ADDRESS_MUX_IR    = 4;
  public static final int ADDRESS_MUX_INDEX = 5;
  
  public static final int DATA_MUX_NONE    = 0;
  public static final int DATA_MUX_REGS    = 1;
  public static final int DATA_MUX_A       = 2;
  public static final int DATA_MUX_F       = 3;
  public static final int DATA_MUX_I       = 4;
  public static final int DATA_MUX_R       = 5;
  public static final int DATA_MUX_ALU     = 6;
  public static final int DATA_MUX_IN      = 7;
  public static final int DATA_MUX_INC_H   = 8;
  public static final int DATA_MUX_INC_L   = 9;
  
  @In @Pins(3) public int sss;
  @In @Pins(3) public int ddd;
  
  @In public boolean dataToRegs;
  @In public boolean dataToA;
  @In public boolean dataToF;
  @In public boolean dataToI;
  @In public boolean dataToR;
  @In public boolean dataToTmp;
  @In public boolean dataToOpcode;
  @In public boolean aToAcu;
  
  @In public boolean increment;
  
  @In public boolean addressToInc;
  @In public boolean incToPc;
  @In public boolean incToSp;
  
  @In public boolean iySelect;
  
  @In public int dataMux;
  @In public int addressMux;
  
  @In public boolean toggleRegs;
  @In public boolean toggleAf;
  
  @In @Pins(8) public int dataIn;
  
  @In public boolean setIff1;
  @In public boolean resetIff1;
  @In public boolean setIff2;
  @In public boolean resetIff2;
  
  @In public boolean addressBusToBuffer;
  @In public boolean dataBusToBuffer;
  
  @Out @Pins(8) public int dataOut;
  @Out @Pins(16) public int addressOut;
  @Out @Pins(8) public int opcodeOut;
  @Out @Pins(8) public int flagsOut;
  @Out public boolean iff1Out;
  @Out public boolean iff2Out;
  @Out @Pins(2) public int imOut;
  
  // Two sets of registers: BC, DE, HL, WZ
  @Internal public boolean regs2Select;
  @Internal public final int[] regs1 = new int[8];
  @Internal public final int[] regs2 = new int[8];
  
  // Two sets of registers: AF
  @Internal public boolean af2Select;
  @Internal public int regA1;
  @Internal public int regF1;
  @Internal public int regA2;
  @Internal public int regF2;
  @Internal public int regTmp;
  @Internal public int acu;
  
  @Internal public int regPc;
  @Internal public int regSp;

  @Internal public int regIx; 
  @Internal public int regIy;
  
  @Internal public int regI;
  @Internal public int regR;
  
  @Internal public int addressBuffer;
  @Internal public int dataBuffer;
  @Internal public int opcodeReg;
  
  @Internal public boolean iff1Reg;
  @Internal public boolean iff2Reg;
  
  @Internal @Pins(16) public int incReg;
  
  @Internal public int imReg;
  
  @Internal public final Alu alu = new Alu();
  
  @Script
  public void update() {
    if (toggleRegs) {
      regs2Select = !regs2Select;
    }
    
    if (toggleAf) {
      af2Select = !af2Select;
    }
    
    if (aToAcu) {
      acu = af2Select ? regA2 : regA1;
    }
    
    if (setIff1) {
      iff1Reg = true;
    }
    
    if (resetIff1) {
      iff1Reg = false;
    }
    
    if (setIff2) {
      iff2Reg = true;
    }
    
    if (resetIff2) {
      iff2Reg = false;
    }
    
    // Update data bus
    int dataBus;
    switch (dataMux) {
      case DATA_MUX_REGS:
        if (regs2Select) {
          dataBus = regs2[sss];
        } else {
          dataBus = regs1[sss];
        }
        break;
      case DATA_MUX_A:
        dataBus = af2Select ? regA2 : regA1;
        break;
      case DATA_MUX_F:
        dataBus = af2Select ? regF2 : regF1;
        break;
      case DATA_MUX_I:
        dataBus = regI;
        break;
      case DATA_MUX_R:
        dataBus = regR;
        break;
      case DATA_MUX_ALU:
        // TODO(micapolos): Verify this code, especially flags handling
        alu.dataIn1 = regTmp;
        alu.dataIn2 = acu;
        alu.op = ddd;
        alu.update();
        dataBus = alu.dataOut;
        break;
      case DATA_MUX_IN:
        dataBus = dataIn;
        break;
      case DATA_MUX_INC_H:
        dataBus = incReg >>> 8;
        break;
      case DATA_MUX_INC_L:
        dataBus = incReg & 0xFF;
        break;
      default:
        dataBus = 0;
        break;
    }
    
    // Update address bus
    int addressBus;
    switch (addressMux) {
      case ADDRESS_MUX_PC:
        addressBus = regPc;
        break;
      case ADDRESS_MUX_SP:
        addressBus = regSp;
        break;
      case ADDRESS_MUX_IR:
        addressBus = (regI << 8) | regR;
        break;
      case ADDRESS_MUX_REGS:
        if (regs2Select) {
          addressBus = (regs2[sss & 0x06] << 8) | (regs2[sss | 0x01]); 
        } else {
          addressBus = (regs1[sss & 0x06] << 8) | (regs1[sss | 0x01]); 
        }
        break;
      case ADDRESS_MUX_INDEX:
        int index = iySelect ? regIy : regIx;
        addressBus = (index + (int) (byte) dataBus) & 0xFFFF;
        break;
      default:
        addressBus = 0;
        break;
    }
    
    if (dataToRegs) {
      if (regs2Select) {
        regs2[ddd] = dataBus;
      } else {
        regs1[ddd] = dataBus;
      }
    }
    
    if (dataToA) {
      if (af2Select) {
        regA2 = dataBus;
      } else {
        regF2 = dataBus;
      }
    }
    
    if (dataToF) {
      if (af2Select) {
        regF2 = dataBus;
      } else {
        regF2 = dataBus;
      }
    }
    
    if (dataToI) {
      regI = dataBus;
    }
    
    if (dataToR) {
      regR = dataBus;
    }
    
    if (dataToTmp) {
      regTmp = dataBus;
    }
   
    if (dataToOpcode) {
      opcodeReg = dataBus;
    }
    
    if (dataBusToBuffer) {
      dataBuffer = dataBus;
    }

    if (addressToInc) {
      incReg = (increment ? addressBus + 1 : addressBus - 1) & 0xFFFF;
    }
    
    if (incToPc) {
      regPc = incReg;
    }
    
    if (incToSp) {
      regSp = incReg;
    }
    
    if (addressBusToBuffer) {
      addressBuffer = addressBus;
    }
    
    // Write out the content of data/address buffer.
    dataOut = dataBuffer;
    addressOut = addressBuffer;
    opcodeOut = opcodeReg;
    iff1Out = iff1Reg;
    iff2Out = iff2Reg;
    flagsOut = af2Select ? regF2 : regF1;
    imOut = imReg;
  }
}
